#!/usr/bin/perl

use strict;
use warnings;
use Digest::MD5 qw(md5);
use File::stat;

my $COV_BASE_HOME = $ENV{"COV_BASE_HOME"};

#die "please set env $COV_BASE_HOME first!"
#    if (!defined($COV_BASE_HOME));

# for my convenience
if (!defined($COV_BASE_HOME)) {
    $COV_BASE_HOME = "/uusoc/exports/scratch/chenyang/branch-test";
}

my $PATH = "$COV_BASE_HOME/bin";
my $COMPILER_SOURCES_HOME = "$COV_BASE_HOME/sources";

# tmp setting on bethe where expect was installed locally
# we don't need those if expect was installed via apt-get, etc
my $EXPECT_BASE_HOME = "$COV_BASE_HOME/expect/usr";

my $VOLATILE_HOME = "$COMPILER_SOURCES_HOME/volatile";

$PATH = "$EXPECT_BASE_HOME/bin:$VOLATILE_HOME:$PATH";

$ENV{"PATH"} = "$PATH:$ENV{'PATH'}";

if (defined($ENV{'LD_LIBRARY_PATH'})) {
    $ENV{"LD_LIBRARY_PATH"} = "$EXPECT_BASE_HOME/lib:$ENV{'LD_LIBRARY_PATH'}";
}
else {
    $ENV{"LD_LIBRARY_PATH"} = "$EXPECT_BASE_HOME/lib";
}

my $CSMITH_HOME = "$COMPILER_SOURCES_HOME/csmith";
my $CSMITH = "$CSMITH_HOME/src/csmith";

my $LCOV_HOME = "$COMPILER_SOURCES_HOME/lcov-1.8/bin";
my $LCOV = "$LCOV_HOME/lcov";
my $LCOV_GEN_HTML = "$LCOV_HOME/genhtml";
my $COV_DATA_HOME = "$COV_BASE_HOME/test_data";

my %compilers = (
    "gcc" => "$COV_BASE_HOME/bin/current-gcc",
    #"clang" => "$COV_BASE_HOME/bin/clang",
    "llvm" => "$COV_BASE_HOME/bin/clang",
);

my %compiler_build_paths = (
    "gcc" => "$COMPILER_SOURCES_HOME/current-gcc/build",
    "llvm" => "$COMPILER_SOURCES_HOME/llvm/build",
    "clang" => "$COMPILER_SOURCES_HOME/llvm/build/tools/clang",
    "llvm-testsuite" => "$COMPILER_SOURCES_HOME/llvm/build/projects/test-suite",
);

my %base_cov_dirs = (
    "gcc" => "$COMPILER_SOURCES_HOME/current-gcc/build/gcc",
);

my %base_checks = (
    "gcc" => ["gcc", "make check-c"],
    "llvm" => ["llvm", "ENABLE_COVERAGE=1 make check", "llvm", "ENABLE_COVERAGE=1 make unittests", "llvm-testsuite", "ENABLE_COVERAGE=1 make", "clang", "ENABLE_COVERAGE=1 make test"],
    #"llvm" => ["llvm-testsuite", "ENABLE_COVERAGE=1 make"],
    #"llvm" => ["clang", "ENABLE_COVERAGE=TRUE make test"],
    "clang" => ["clang", "ENABLE_COVERAGE=1 make test"],
);

my %compilation_opts = (
    "gcc" => [ "-O0",
               "-O1",
               "-O2 -march=core2 -mtune=core2 -fno-stack-protector -fomit-frame-pointer",
               "-Os",
               "-O3 -fwhole-program -combine"],
    "clang" => [ "-O0",
                 "-O1",
                 "-O2  -march=core2 -mtune=core2 -fomit-frame-pointer -fno-stack-protector",
                 "-Os",
                 "-O3"],
    "llvm" => [ "-O0",
                 "-O1",
                 "-O2  -march=core2 -mtune=core2 -fomit-frame-pointer -fno-stack-protector",
                 "-Os",
                 "-O3"],
);

# the number of testcases generated by csmith
my $NUMBER_OF_TESTCASES = 10000;

my $CSMITH_OPTS = " --no-paranoid --bitfields ia32 --packed-struct ";

my $CSMITH_TIMEOUT = 60;

my $COMPILER_TIMEOUT = 600;

my $MIN_PROGRAM_SIZE = 10000;

my $CSMITH_OUT = "$COV_DATA_HOME/csmith_out";

my $TEST_FAILS_OUT = "$COV_DATA_HOME/compilers_fail_out.txt";

my %prog_checksums = ();

sub runit ($) {
    my $cmd = shift;
    my $res = (system "$cmd");
    my $exit_value  = $? >> 8;
    return $exit_value;
}

sub gen_lcov_info($$$) {
    my ($compiler, $build_path, $lcov_info) = @_;

    system "rm -rf $lcov_info";
    my $cmd;
    my $base_cov_dir = $base_cov_dirs{$compiler};
    
    if (defined($base_cov_dir)) {
        $cmd = "$LCOV -c -d $build_path -b $base_cov_dir -o $lcov_info";
    }
    else {
        $cmd = "$LCOV -c -d $build_path -o $lcov_info";
    }
    die "Can't generate lcov info for $compiler"
        if ((system "$cmd") != 0);

    die "Can't generate $lcov_info"
        if (!(-f $lcov_info));
}

sub gen_base_cov_data($) {
    my ($compiler) = @_;
    my $build_path = $compiler_build_paths{$compiler};
    my $checks = $base_checks{$compiler};

    # clear old gcdas
    die if ((system "$LCOV -z -d $build_path") != 0);

    print "changing to $build_path\n";
    chdir $build_path or die;
    # generate baseline cov data
    #foreach my $check (@$checks) {
    #    system "$check";
    #}
    for (my $i = 0; $i < @$checks; $i = $i + 2) {
        my $check_path = $compiler_build_paths{@$checks[$i]};
        print "chaning to $check_path\n";
        chdir $check_path or die;
        system "@$checks[$i+1]";
    }

    print "changing to $compiler_build_paths{$compiler}\n";
    chdir $compiler_build_paths{$compiler} or die;

    my $cmd = "find $build_path -name '*.gcda'";
    my @gcdas = `$cmd`;
    die "no gcdas!" if (@gcdas == 0);
}

# 0 -> good
sub is_good_prog($) {
    my ($cfile) = @_;

    my $filesize = stat("$cfile")->size;
    if ($filesize < $MIN_PROGRAM_SIZE) {
        return (-1, "");
    }

    open INF, "<$cfile" or die;
    my $prog = "";
    my $seed = "";
    while (my $line = <INF>) {
        chomp $line;
        if ($line =~ /Seed:\s+([0-9]+)$/) {
            $seed = $1;
        }
        $prog .= "$line ";
    }
    close INF;

    ($prog =~ s/\/\*(.*?)\*\///g);
    my $digest = md5($prog);
    if ($prog_checksums{$digest}) {
        return (-1, "");
    }

    return (0, $seed);
}

sub test_compilers($) {
    my ($cfile) = @_;

    my $res_str = "";
    my $res_code = 0;

    foreach my $compiler (keys %compilers) {
        my $compiler_exec = $compilers{$compiler};
        my $opts = $compilation_opts{$compiler};
        foreach my $opt (@$opts) {
            my $opt_str = $opt;
            ($opt_str =~ s/\ //g);
            ($opt_str =~ s/\://g);
            ($opt_str =~ s/\-//g);
            ($opt_str =~ s/\///g);
            if (length($opt_str)>40) {
                $opt_str = substr ($opt_str, 0, 40);
            }

            my $exe = "${compiler}${opt_str}_exe";
            ($exe =~ s/\.//g);
            my $extra_opts = " -w -DINLINE=";
            my $out = "rand.out";
            my $compilerout = "${exe}_compiler.out";

            my $command = "RunSafely.sh $COMPILER_TIMEOUT 1 /dev/null $compilerout $compiler_exec $opt $extra_opts $cfile -o $exe -I${CSMITH_HOME}/runtime";

            my $res = runit ($command);
            if (($res != 0) || (!(-e $exe))) {
                $res_code = -1;
                if ($res == 137) {
                    $res_str .= "COMPILER FAILURE: TIMEOUT\n";
                } else {
                    $res_str .= "COMPILER FAILURE with return code $res; output is:\n";
                    open INF, "<$compilerout" or die;
                    while (my $line = <INF>) { $res_str .= "  $line"; }
                    close INF;
                }
            }
        }
    }

    return ($res_code, $res_str);
}

sub do_one_test() {
    my $cfile = "rand.c";
    print "chaning to $CSMITH_OUT\n";
    chdir $CSMITH_OUT or die;

    system "rm -rf ./*";
    my $cmd = "$CSMITH_HOME/src/csmith $CSMITH_OPTS --output $cfile";
    #print "$cmd\n";
    my $res = runit ("RunSafely.sh $CSMITH_TIMEOUT 1 /dev/zero csmith_output.txt $cmd");

    my $seed;
    ($res, $seed) = is_good_prog($cfile);
    if ($res != 0) {
        return -1;
    }

    if ($res != 0 || !(-e "$cfile")) {
        open OUTF, ">>$TEST_FAILS_OUT" or die;
        print OUTF "csmith FAILS with seed: $seed\n";
        close OUTF;
        return -1;
    }

    my $res_str;
    ($res, $res_str) = test_compilers($cfile);
    if ($res != 0) {
        open OUTF, ">>$TEST_FAILS_OUT" or die;
        print OUTF "TESTING COMPILER FAILS with seed: $seed\n";
        print OUTF "$res_str\n";
        close OUTF;
    }
    return 0;
}

sub run_csmith() {
    if (-d $CSMITH_OUT) {
        system "rm -rf $CSMITH_OUT/*";
    }
    else {
        mkdir $CSMITH_OUT;
    }

    my $count = 0;
    system "rm -rf $TEST_FAILS_OUT";

    while ($count < $NUMBER_OF_TESTCASES) {
       my $res;
       $res = do_one_test();
       if ($res == 0) {
           $count++;
           print "Successfully ran csmith[$count]\n";
       }
    }
}

sub gen_lcov_html($$$) {
    my ($summary, $html_outdir, $lcov_info) = @_;

    if (!(-d $html_outdir)) {
        mkdir $html_outdir;
    }
    else {
        system "rm -rf $html_outdir/*";
    }

    my $cmd = "$LCOV_GEN_HTML $lcov_info -o $html_outdir --summary-output=$summary";

    die "Can't generate html info for $lcov_info"
        if ((system "$cmd") != 0);
}

sub gen_extended_cov() {
    foreach my $compiler (keys %compilers) {
        my $build_path = $compiler_build_paths{$compiler};
        my $extended_lcov_info = "$COV_DATA_HOME/$compiler" . "_extended.info";
        my $html_outdir = "$COV_DATA_HOME/${compiler}_html_ext";
        my $summary = "$COV_DATA_HOME/${compiler}_summary_ext.txt";

        gen_lcov_info($compiler, $build_path, $extended_lcov_info);
        gen_lcov_html($summary, $html_outdir, $extended_lcov_info);
    }
}

sub gen_base_cov() {
    foreach my $compiler (keys %compilers) {
        my $build_path = $compiler_build_paths{$compiler};
        my $base_lcov_info = "$COV_DATA_HOME/$compiler.info";
        my $html_outdir = "$COV_DATA_HOME/${compiler}_html";
        my $summary = "$COV_DATA_HOME/${compiler}_summary.txt";

        gen_base_cov_data($compiler);
        gen_lcov_info($compiler, $build_path, $base_lcov_info);
        gen_lcov_html($summary, $html_outdir, $base_lcov_info);
    }
}

if (!(-d $COV_DATA_HOME)) {
    mkdir "$COV_DATA_HOME";
}

gen_base_cov();
run_csmith();
gen_extended_cov();

